#include <iostream>
#include <pthread.h>
#include <queue>
#include <unistd.h>
#include <functional>

#define MAX_CAPACITY 6

using func_t = std::function<void()>; //是void,不是void*

template <typename T>
class BlockQueue
{
public:
    // 构造 (初始化锁和条件变量)
    BlockQueue()
        : _capacity(MAX_CAPACITY), _c_sleepNum(0), _p_sleepNum(0)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_isFull, nullptr);
        pthread_cond_init(&_isEmpty, nullptr);
    }
    // 消费
    T Consume()
    {
        //对临界资源判断的操作也得是原子的,属于临界区
        pthread_mutex_lock(&_mutex);
        while (_queue.empty())
        {
            //运行到此处,说明:1.线程拿到了锁 2.此时没有资源可消费 3.线程必须等待资源被生产
            _c_sleepNum++;
            pthread_cond_wait(&_isEmpty,&_mutex);
            _c_sleepNum--;
        }
        //继续消费
        T ret = _queue.front();
        _queue.pop();
        //消费后尝试唤醒生产者
        if(_p_sleepNum>0)
        {
            //std::cout << "尝试唤醒生产者" << std::endl;
            pthread_cond_signal(&_isFull);
        }
        pthread_mutex_unlock(&_mutex);
        return ret;
    }
    // 生产
    void Produce( T Raw_materail)
    {   
        pthread_mutex_lock(&_mutex);
        while(_queue.size() >= _capacity)
        {
            //运行到此处,说明:1.线程拿到了锁2.此时临界区资源满了,不能再生产3.线程必须等待资源被消费
            _p_sleepNum++;
            pthread_cond_wait(&_isFull,&_mutex);
            _p_sleepNum--;
        }
        //继续生产
        _queue.push(Raw_materail);
        //生产后尝试唤醒消费者
        if(_c_sleepNum>0)
        {
            //std::cout << "尝试唤醒消费者" << std::endl;
            pthread_cond_signal(&_isEmpty);
        }
        pthread_mutex_unlock(&_mutex);
    }

    // 析构 (销毁锁和条件变量)
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_isFull);
        pthread_cond_destroy(&_isEmpty);
    }

    
private:
    std::queue<T> _queue; // 临界资源
    int _capacity;        // 记录容量,防止容器扩容
    pthread_cond_t _isFull;
    pthread_cond_t _isEmpty;
    pthread_mutex_t _mutex;

    int _c_sleepNum;
    int _p_sleepNum;
};